#include <string.h>
#include <opencv/highgui.h>
#include <opencv2/opencv.hpp>
#include "GeoTiffImage.hxx"

#define MAX_OPENCV_IMAGE_SIZE 20000


GeoTiffImage::GeoTiffImage()
{
	_imageData = nullptr;
	_imageWidth = 0;
	_imageHeight = 0;
	_channels = 0;

	_minValue = 10000.0f;
	_maxValue = -10000.0f;

	_geoScale[0] = _geoScale[1] = _geoScale[2] = 0.0;
	for (int i = 0; i < 6; i++) _geoControlPoint[i] = 0.0f;
}

GeoTiffImage::~GeoTiffImage()
{
	if (_imageData)
	{
		free(_imageData);
	}

	_imageData = nullptr;
	_imageWidth = 0;
	_imageHeight = 0;
	_channels = 0;
	_depth = 8;
}

void GeoTiffImage::refreshDataSpace()
{
	if (_imageData)
	{
		free(_imageData);
		_imageData = nullptr;
	}

	assert(_imageWidth > 0 && _imageHeight > 0 && _channels > 0 && "Wrong Image Parameters!!!");

	_imageData = (unsigned char*)malloc(_imageWidth * _imageHeight * _channels * ( _depth / 8 ));
}

IplImage* GeoTiffImage::convert2IplImage()
{
	IplImage* image = cvCreateImage(cvSize(_imageWidth, _imageHeight), _depth, _channels);

	//float vmin = 10000;

	//float vmax = -10000.0f;

	//if (_depth == 32 && _channels == 1 )
	//{
	//	for (int y = 0; y < image->height; y++)
	//	{
	//		for (int x = 0; x < image->width; x++)
	//		{
	//			float* val = (float*)(image->imageData + y * image->widthStep + x * _channels * (_depth / 8));

	//			if (*val == -32767.0) continue;

	//			if (*val > vmax) vmax = *val;
	//			if (*val < vmin) vmin = *val;
	//		}
	//	}

	//	for (int y = 0; y < image->height; y++)
	//	{
	//		for (int x = 0; x < image->width; x++)
	//		{
	//			float* val = (float*)(image->imageData + y * image->widthStep + x * _channels * (_depth / 8));
	//			unsigned char* cval = (unsigned char*)(imageForShow->imageData + y * imageForShow->widthStep + x * _channels);

	//			if (*val == -32767.0)
	//			{
	//				*val = 0.0f;
	//				continue;
	//			}

	//			//printf("%f\n", *val);

	//			//*val = (*val - vmin) / (vmax - vmin);
	//			*cval = (*val - vmin) / (vmax - vmin) * 255.0f;

	//			//printf("%d\n", *cval);
	//		}
	//	}
	//}
	//else
	//{
		for (int y = 0; y < _imageHeight; y++)
		{
			char* pDst = image->imageData + y * image->widthStep;
			char* pSrc = (char*)_imageData + y * _imageWidth * _channels * _depth / 8;
			memcpy(pDst, pSrc, _imageWidth * _channels * _depth / 8);
		}
	//}


	//printf("Max:%f\t Min:%f\n", vmax, vmin);
	return image;
}

void GeoTiffImage::resizeTo(GeoTiffImage* target, int width, int height)
{
	target->setWidth(width);
	target->setHeight(height);
	target->setChannels(_channels);
	target->setDepth(_depth);
	target->setMinValue(_minValue);
	target->setMaxValue(_maxValue);

	target->refreshDataSpace();

	float scaleX = _geoScale[ 0 ] * width * 1.0 / _imageWidth;
	float scaleY = _geoScale[1] * height * 1.0 / _imageHeight;

	target->setGeoScale(scaleX, scaleY, _geoScale[2]);
	target->setGeoControlPoint(_geoControlPoint);

	if (_imageWidth < MAX_OPENCV_IMAGE_SIZE && _imageHeight < MAX_OPENCV_IMAGE_SIZE)
	{
		IplImage* src = cvCreateImageHeader(cvSize(_imageWidth, _imageHeight), _depth, _channels);
		src->imageData = (char*)_imageData;

		IplImage* dst = cvCreateImageHeader(cvSize(width, height), _depth, _channels);
		dst->imageData = (char*)target->getImageData();

		cvResize(src, dst);


		cvReleaseImageHeader(&src);
		cvReleaseImageHeader(&dst);
	}
	else
	{
		int tileCols = _imageWidth / MAX_OPENCV_IMAGE_SIZE;
		if (_imageWidth % MAX_OPENCV_IMAGE_SIZE > 0) tileCols += 1;

		int tileRows = _imageHeight / MAX_OPENCV_IMAGE_SIZE;
		if (_imageHeight % MAX_OPENCV_IMAGE_SIZE > 0) tileRows += 1;

		float scaleX = target->getWidth() * 1.0 / _imageWidth;
		float scaleY = target->getHeight() * 1.0 / _imageHeight;

		for (int ty = 0; ty < tileRows; ty++)
		{
			for (int tx = 0; tx < tileCols; tx++)
			{
				int tileWidth = 0;
				int tileHeight = 0;

				if (ty < tileRows - 1)
					tileHeight = MAX_OPENCV_IMAGE_SIZE;
				else
					tileHeight = _imageHeight - (tileRows - 1) * MAX_OPENCV_IMAGE_SIZE;

				if (tx < tileCols - 1)
					tileWidth = MAX_OPENCV_IMAGE_SIZE;
				else
					tileWidth = _imageWidth - (tileCols - 1) * MAX_OPENCV_IMAGE_SIZE;

				int dstTileWidth = tileWidth * scaleX + 0.5f;
				int dstTileHeight = tileHeight * scaleY + 0.5f;

				char* srcTileData = (char*)malloc(tileWidth * tileHeight * _channels * (_depth / 8));
				char* dstTileData = (char*)malloc(dstTileWidth * dstTileHeight * _channels * (_depth / 8));

				// Copy data to src tile buffer
				for (int y = 0; y < tileHeight; y++)
				{
					int sy = ty * MAX_OPENCV_IMAGE_SIZE + y;
					int sx = tx * MAX_OPENCV_IMAGE_SIZE;

					char* pSrc = (char*)_imageData + ( sy * _imageWidth + sx ) * _channels * (_depth / 8);
					char* pDst = srcTileData + y * tileWidth * _channels * (_depth / 8);

					memcpy(pDst, pSrc, tileWidth * _channels * (_depth / 8));
				}

				// resize tile
				IplImage* src = cvCreateImageHeader(cvSize(tileWidth, tileHeight), _depth, _channels);
				src->imageData = (char*)srcTileData;


				IplImage* dst = cvCreateImageHeader(cvSize(dstTileWidth, dstTileHeight), _depth, _channels);
				dst->imageData = (char*)dstTileData;

				cvResize(src, dst);	

				// copy resized data to target tiff image
				for (int y = 0; y < dstTileHeight; y++)
				{
					int sy = ty * (MAX_OPENCV_IMAGE_SIZE * scaleY) + y;
					int sx = tx * (MAX_OPENCV_IMAGE_SIZE * scaleX);
					char* pSrc = dstTileData + y * dstTileWidth * _channels * _depth / 8;
					char* pDst = (char*)target->getImageData() + ( sy * width + sx )  * _depth * _channels /8;

					memcpy(pDst,pSrc, dstTileWidth * _channels * _depth / 8);
					//memcpy(pDst, buf, dstTileWidth * _channels * _depth / 8);

				}

				//memcpy(target->getImageData(), dstTileData, dstTileHeight * dstTileWidth * _channels * _depth / 8);
	
				cvReleaseImageHeader(&src);
				cvReleaseImageHeader(&dst);

				free(srcTileData);
				free(dstTileData);
			}
		}
	}
}

void GeoTiffImage::splitImage(const char* imageDirectory)
{
	int patchWidth = 1024;
	int patchHeight = 1024;

	cv::Mat image = cv::Mat::zeros(patchHeight, patchWidth, CV_8UC4);

	std::vector<cv::Mat> matArray;

	int trows = 0;
	int tcols = 0;

	if (_imageHeight % patchHeight == 0)
		trows = _imageHeight / patchHeight;
	else
		trows = _imageHeight / patchHeight + 1;

	if (_imageWidth % patchWidth == 0)
		tcols = _imageWidth / patchWidth;
	else
		tcols = _imageWidth / patchWidth + 1;

	for (int r = 0; r < trows; r++)
	{
		for (int c = 0; c < tcols; c++)
		{
			int rx = c * patchWidth;

			int lnSize = 0;

			if ((c + 1) * patchWidth <= _imageWidth)
				lnSize = patchWidth * _channels;
			else
				lnSize = (_imageWidth - c * patchWidth) * _channels;

			for (int y = 0; y < patchHeight; y++)
			{
				if (r * patchHeight + y >= _imageHeight)
					break;

				uchar* dst = image.data + y * image.cols * _channels;

				int ry = r * patchHeight + y;

				uchar* src = _imageData + (ry * _imageWidth + rx) * _channels;

				memcpy(dst, src, lnSize);
			}

			cv::split(image, matArray);

			// Test empty image
			double minVal[3];
			double maxVal[3];

			cv::Point minLoc[3];
			cv::Point maxLoc[3];

			for (int i = 0; i < 3; i++)
				cv::minMaxLoc(matArray[i], &minVal[i], &maxVal[i], &minLoc[i], &maxLoc[i]);

			if (minVal[0] > 250 && minVal[1] > 250 && minVal[2] > 250)
			{
				printf("Skipping empty Tile R:%d/%d C:%d/%d.\n", r, trows, c, tcols);
				continue;
			}

			std::vector<cv::Mat> tmpArray = matArray;
			tmpArray[0] = matArray[2];
			tmpArray[2] = matArray[0];

			cv::merge(tmpArray, image);

			char fileName[256];
			sprintf(fileName, "%s/Tile_R%03d_C%03d.JPG", imageDirectory, r, c);

			printf("Exporting Tile R:%d/%d C:%d/%d.\n", r, trows, c, tcols);

			cv::imwrite(fileName, image);
		}
	}
}
